﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection.Emit;
using System.Reflection;

namespace ExpLib.Core.AOP
{
    public class DynamicProxy
    {
        const string ASSEMBLY_NAME = "TempAssemblyInjection";
        const string MODULE_NAME = "Harold.Net.Emit.Aop.DynamicModule";
        const string TYPTENAME_FORMAT = "Harold.Net.Emit.Aop.Dynamic{0}Type";
            
        #region 私有成员
        private AssemblyBuilder m_AssemblyBuilder;

        private ModuleBuilder m_ModuleBuilder;

        private TypeBuilder m_TypeBuilder;

        private Type m_DeclareType;
        private Type m_RealProxyType;
        private Type m_InterceptorType;

        private FieldBuilder m_RealProxyField;
        private FieldBuilder m_InterceptorField;
        #endregion

       

        void BuildAssembly()
        {
            // assembly name
            AssemblyName assemblyName = new AssemblyName(ASSEMBLY_NAME);

            m_AssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName,
                AssemblyBuilderAccess.RunAndSave, System.AppDomain.CurrentDomain.BaseDirectory);

            m_ModuleBuilder = m_AssemblyBuilder.DefineDynamicModule(MODULE_NAME, ASSEMBLY_NAME + ".DLL");

            if (!Type.Equals(m_DeclareType, m_RealProxyType))
            {
                m_TypeBuilder = m_ModuleBuilder.DefineType(string.Format(TYPTENAME_FORMAT, m_RealProxyType.Name), TypeAttributes.Public | TypeAttributes.Sealed);
                m_TypeBuilder.AddInterfaceImplementation(m_DeclareType);
            }
            else
            {
                m_TypeBuilder = m_ModuleBuilder.DefineType(string.Format(TYPTENAME_FORMAT, m_RealProxyType.Name), TypeAttributes.Public | TypeAttributes.Sealed, m_DeclareType);
            }        

        }


        void BuildConstructor()
        {
            ConstructorBuilder constructorBuilder = m_TypeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, null);
            ILGenerator generator = constructorBuilder.GetILGenerator();

            // _realProxy = new RealProxy();
            generator.Emit(OpCodes.Ldarg_0);
            ConstructorInfo defaultConstructorInfo = m_RealProxyType.GetConstructor(Type.EmptyTypes);
            generator.Emit(OpCodes.Newobj, defaultConstructorInfo);
            generator.Emit(OpCodes.Stfld, m_RealProxyType);

            // _intercepter = new BaseInterceptor();
            generator.Emit(OpCodes.Ldarg_0);
            defaultConstructorInfo = m_InterceptorType.GetConstructor(Type.EmptyTypes);
            generator.Emit(OpCodes.Newobj, defaultConstructorInfo);
            generator.Emit(OpCodes.Stfld, m_InterceptorType);

            generator.Emit(OpCodes.Ret);
        }

        void BuildField()
        {
            m_InterceptorField = m_TypeBuilder.DefineField("_interceptor", m_InterceptorType, FieldAttributes.Private);
            m_InterceptorField.SetConstant(null);

            m_RealProxyField = m_TypeBuilder.DefineField("_realProxy", m_RealProxyType, FieldAttributes.Private);
            m_RealProxyField.SetConstant(null);
        }


        void BuildMethods()
        {
            MethodInfo[] methodInfos = m_DeclareType.GetMethods(BindingFlags.Public | BindingFlags.Instance);
            foreach (MethodInfo methodInfo in methodInfos)
            {
                BuildMethod(methodInfo);
            }
        }

         void BuildMethod(MethodInfo methodInfo)
        {
            string methodName = methodInfo.Name;
            ParameterInfo[] parameterInfos = methodInfo.GetParameters();
            Type returnType = methodInfo.ReturnType;

            MethodBuilder methodBuilder = null;

            if (!Type.Equals(m_DeclareType, m_RealProxyType))
            {
                methodBuilder = m_TypeBuilder.DefineMethod(methodName, MethodAttributes.Public | MethodAttributes.Virtual, returnType, parameterInfos.Select(pi => pi.ParameterType).ToArray());
            }
            else
            {
                methodBuilder = m_TypeBuilder.DefineMethod(methodName, MethodAttributes.Public, returnType, parameterInfos.Select(pi => pi.ParameterType).ToArray());
            }

            var generator = methodBuilder.GetILGenerator();

            #region Decalre InvokeContext

            Type contextType = typeof(MethodContext);
            var contextLocal = generator.DeclareLocal(contextType);
            generator.Emit(OpCodes.Newobj, contextType.GetConstructor(Type.EmptyTypes));
            generator.Emit(OpCodes.Stloc, contextLocal);

            // set method name
            generator.Emit(OpCodes.Ldloc, contextLocal);
            generator.Emit(OpCodes.Ldstr, methodName);
            generator.Emit(OpCodes.Call, contextType.GetMethod("SetMethod", BindingFlags.Public | BindingFlags.Instance));

            #endregion

            #region Decalre result

            LocalBuilder resultLocal = null;

            if (returnType != typeof(void))
            {
                resultLocal = generator.DeclareLocal(returnType);
                if (returnType.IsValueType)
                {
                    generator.Emit(OpCodes.Ldstr, returnType.FullName);
                    generator.Emit(OpCodes.Call, typeof(Type).GetMethod("GetType", new Type[] { typeof(string) }));
                    generator.Emit(OpCodes.Call, typeof(Activator).GetMethod("CreateInstance", new Type[] { typeof(Type) }));
                }
                else
                {
                    generator.Emit(OpCodes.Ldnull);
                }

                generator.Emit(OpCodes.Stloc, resultLocal);
            }

            #endregion

            #region Declare Exception

            var exceptionLocal = generator.DeclareLocal(typeof(Exception));
            generator.Emit(OpCodes.Ldnull);
            generator.Emit(OpCodes.Stloc, exceptionLocal);
            #endregion

            #region Invoke PreInvoke

            #region Set parameter to InvkeContext

            for (int i = 1; i <= parameterInfos.Length; i++)
            {
                generator.Emit(OpCodes.Ldloc, contextLocal);
                generator.Emit(OpCodes.Ldarg, i);
                if (parameterInfos[i - 1].ParameterType.IsValueType)
                {
                    generator.Emit(OpCodes.Box, parameterInfos[i - 1].ParameterType);
                }
                generator.Emit(OpCodes.Call, contextType.GetMethod("SetParameter", BindingFlags.Public | BindingFlags.Instance));
            }

            #endregion

            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Ldfld, m_InterceptorField);
            generator.Emit(OpCodes.Ldloc, contextLocal);
            generator.Emit(OpCodes.Callvirt, m_InterceptorType.GetMethod("PreInvoke", BindingFlags.Public | BindingFlags.Instance));

            #endregion

            #region Begin Exception Block

            generator.BeginExceptionBlock();

            #endregion

            #region Invoke

            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Ldfld, m_RealProxyField);
            for (int i = 1; i <= parameterInfos.Length; i++)
            {
                generator.Emit(OpCodes.Ldarg, i);
            }
            generator.Emit(OpCodes.Call, m_RealProxyType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance));
            if (typeof(void) != returnType)
            {
                generator.Emit(OpCodes.Stloc, resultLocal);
            }

            #endregion

            #region Invoke PostInovke

            #region Set result to InvkeContext

            generator.Emit(OpCodes.Ldloc, contextLocal);
            // load parameter
            if (typeof(void) != returnType)
            {
                generator.Emit(OpCodes.Ldloc, resultLocal);
                if (returnType.IsValueType)
                {
                    generator.Emit(OpCodes.Box, returnType);
                }
            }
            else
            {
                generator.Emit(OpCodes.Ldnull);
            }
            generator.Emit(OpCodes.Call, contextType.GetMethod("SetResult", BindingFlags.Public | BindingFlags.Instance));

            #endregion

            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Ldfld, m_InterceptorField);
            generator.Emit(OpCodes.Ldloc, contextLocal);
            generator.Emit(OpCodes.Callvirt, m_InterceptorType.GetMethod("PostInvoke", BindingFlags.Public | BindingFlags.Instance));

            #endregion

            #region Catch Block

            generator.BeginCatchBlock(typeof(Exception));

            generator.Emit(OpCodes.Stloc, exceptionLocal);
            generator.Emit(OpCodes.Ldloc, contextLocal);
            generator.Emit(OpCodes.Ldloc, exceptionLocal);
            generator.Emit(OpCodes.Call, contextType.GetMethod("SetError", BindingFlags.Public | BindingFlags.Instance));

            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Ldfld, m_InterceptorField);
            generator.Emit(OpCodes.Ldloc, contextLocal);
            generator.Emit(OpCodes.Callvirt, m_InterceptorType.GetMethod("ExceptionInvoke", BindingFlags.Public | BindingFlags.Instance));

            #endregion

            #region End Exception Block

            generator.EndExceptionBlock();

            #endregion

            if (typeof(void) != returnType)
            {
                generator.Emit(OpCodes.Ldloc, resultLocal);
            }

            generator.Emit(OpCodes.Ret);
        }  
    
    }
}
